package server;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.spec.DHParameterSpec;

import messages.ChatMessage;
import messages.DHMessage;
import messages.DHResponseMessage;
import messages.LoginMessage;
import messages.LoginResponseMessage;
import messages.Message;
import util.Connection;
import util.Network;

public class ReadThread implements Runnable {

	private ConcurrentLinkedQueue<Connection> readQ;
	private ConcurrentLinkedQueue<MessageInfo> sendQ;
	private Collection<Connection> connectionVector, beingHandled;
	
	private static HashMap<String, String> accounts;
	static {
		accounts = new HashMap<String, String>();
		//user#:password#, salt = "38&!"
		accounts.put("user1", "fa08bdf3b35403311e6e381f7bd699df");
		accounts.put("user2", "14e699b1c13aa1145cb5bfa69185bfb5");
		accounts.put("user3", "7e335738b6b70075bd257d449311d1b3");
		accounts.put("user4", "24ab06b9211dc779ce1538261c449400");
	}
	
	public ReadThread(ConcurrentLinkedQueue<Connection> readQ, 
					ConcurrentLinkedQueue<MessageInfo> sendQ,
					Collection<Connection> connectionVector, 
					Collection<Connection> beingHandled) {
		this.readQ = readQ;
		this.sendQ = sendQ;
		this.connectionVector = connectionVector;
		this.beingHandled = beingHandled;
	}
	
	
	public void run() {
		for(;;) {
			
			while(readQ.isEmpty()) {
				synchronized(readQ) {
					try {
						readQ.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			System.out.println("ReadThread: new traffic detected");
			Connection conn = readQ.poll();						
			
			try {
				
				byte[] bytesrcvd = Network.readBytes(conn.getSocket());
				if(conn.getKey() != null) {
					System.out.println("Bytes rcvd: " + bytesrcvd.length);
					bytesrcvd = Network.decrypt(conn.getKey(), bytesrcvd);
				}
				Message msg = Network.unserialize(bytesrcvd);
				System.out.println("ReadThread: message read");
				process(conn, msg);
				
			} catch (IOException io) {
				io.printStackTrace();
			} catch (ClassNotFoundException cnf) {
				cnf.printStackTrace();
			}
			
		}
	}

	private void process(Connection conn, Message msg) {
		System.out.println("ReadThread: processing message");
		if(msg instanceof ChatMessage && conn.getSessionID() == msg.getSessionID()) {
			ChatMessage cm = (ChatMessage)msg;
			String chat = cm.getMessage();
			if(isPrintable(chat)) {
				for(Connection c : connectionVector) {
					MessageInfo mp = new MessageInfo(c, msg);
					sendQ.add(mp);
					synchronized(sendQ) {
						sendQ.notify();
					}
				}
			} //else silently discard
		}
		
		if(msg instanceof LoginMessage) {
			LoginMessage lm = (LoginMessage)msg;
			String username = lm.getSender();
			String pwHash = lm.getPwHash();
			
			long sID = -1L;
			System.out.println(pwHash + "\n" + accounts.get(username));
			if(pwHash.equals(accounts.get(username))) {
				Random rand = new Random();
				sID = rand.nextLong();
				conn.setSessionID(sID);	
				conn.setUsername(username);
			}
			
			LoginResponseMessage lrm = new LoginResponseMessage(conn.getUsername(), sID);
			MessageInfo mp = new MessageInfo(conn, lrm);
			sendQ.add(mp);
			
			synchronized(sendQ) {
				sendQ.notify();
			}
		}
		if(msg instanceof DHMessage){
			
			DHMessage dhm = (DHMessage) msg;
			//System.out.println("Is dhmessage");
			DHParameterSpec dhParams = new DHParameterSpec(dhm.getP(), dhm.getG(), dhm.getL());
			
			//System.out.println("P: " + dhm.getP() + "\nG:" + dhm.getG() + "\nL: "+ dhm.getL());
			
			//generate keys
			try {
				KeyPairGenerator keygen = KeyPairGenerator.getInstance("DH");
				keygen.initialize(dhParams, new SecureRandom());
			
				KeyAgreement server_agreement = KeyAgreement.getInstance("DH");
				KeyPair server_keys = keygen.generateKeyPair();
			
				server_agreement.init(server_keys.getPrivate());			
				PublicKey client_key = dhm.getClientPublicKey();			
				server_agreement.doPhase(client_key, true);
				
				SecretKey key = server_agreement.generateSecret("AES");
				conn.setKey(key);
				//System.out.println("Key: "+ Network.toHexString(conn.getKey().getEncoded()));
				
				DHResponseMessage lrm = new DHResponseMessage("", -1, server_keys.getPublic());
				MessageInfo mp = new MessageInfo(conn, lrm);
				sendQ.add(mp);
				
			} catch(NoSuchAlgorithmException nsa) {
				nsa.printStackTrace();
			} catch (InvalidAlgorithmParameterException e) {
				e.printStackTrace();
			} catch (InvalidKeyException e) {
				e.printStackTrace();
			}
			
			synchronized(sendQ) {
				sendQ.notify();
			}
		}
		
		synchronized(beingHandled) {
			beingHandled.remove(conn);
		}
	}
	
	private boolean isPrintable(String chat) {
		for(char c : chat.toCharArray()) {
			if(c >= 32 && c < 127) {
				continue;
			} else {
				return false;
			}
		}
		return true;	    
	}

}
